/*
 *  $Id: Edit.cs 1232 2010-12-07 13:16:37Z thenn.erannor $
 *  This file is a part of Ragima CCG.
 *  (C) 2008-2010 Ragima development group.
 *
 *  Licensed under the terms of the GNU GPL License version 2.
 *
 */

using System;

using AgateLib.DisplayLib;
using AgateLib.Geometry;
using AgateLib.InputLib;

using Nereal.Serialization;

using Ragima.Helper;

namespace Ragima.UI {
    /// <summary>
    /// Редактор: виджет для текстового ввода.
    /// </summary>
    [TypeId("edit")]
    public class Edit : Widget {
        private const int CursorBlinkTime = 20;
        private static readonly Color DefaultEditTextColor = Color.White;
        private static readonly Color DefaultEditBackColor = 0x404040.ToColor();

        private string _value;
        private int _start, _pos;
        private string _editedText, _visibleText;
        private bool _cursorVisible;
        private int _cursorBlinkTimer;

        public Edit() {
            _value = string.Empty;
        }

        /// <summary>
        /// Текущее текстовое значение.
        /// </summary>
        [XmlName("value"), DefaultValue("")]
        public string Value {
            get { return _value; }
            set {
                _value = GetFirstLine(value);
                _editedText = _value;
                CursorHome();
            }
        }

        /// <summary>
        /// Фоновый цвет по умолчанию для текстового поля.
        /// </summary>
        protected override Nullable<Color> DefaultBackColor {
            get { return DefaultEditBackColor; }
        }

        #region Helper properties
        /// <summary>
        /// Начальная позиция видимого текста.
        /// </summary>
        private int StartPosition {
            get { return _start; }
            set {
                _start = Math.Max(Math.Min(value, _value.Length), 0);
                _visibleText = null;
            }
        }

        /// <summary>
        /// Позиция курсора.
        /// </summary>
        private int CursorPosition {
            get { return _start + _pos; }
        }

        /// <summary>
        /// Ширина области редактора.
        /// </summary>
        private int EditWidth {
            get { return Width - 5; }
        }

        /// <summary>
        /// Редактируемый текст.
        /// </summary>
        private string EditedText {
            get { return _editedText; }
            set {
                _editedText = value;
                _visibleText = null;
            }
        }

        /// <summary>
        /// Длина редактируемого текста.
        /// </summary>
        private int EditedTextLength {
            get { return _editedText.Length; }
        }

        /// <summary>
        /// Видимый текст.
        /// </summary>
        private string VisibleText {
            get {
                if (_visibleText == null)
                    _visibleText = Font != null ? Font.GetTextByWidth(EditedText, _start, EditWidth) : null;
                return _visibleText;
            }
        }

        /// <summary>
        /// Длина видимого текста.
        /// </summary>
        private int VisibleTextLength {
            get {
                if (_visibleText != null)
                    return _visibleText.Length;
                if (Font == null)
                    return 0;
                int end = Font.GetPositionByWidth(EditedText, _start, EditWidth);
                return end - _start;
            }
        }

        /// <summary>
        /// Наличие видимого текста.
        /// </summary>
        private bool HasVisibleText {
            get { return !string.IsNullOrEmpty(VisibleText); }
        }

        /// <summary>
        /// Цвет текста.
        /// </summary>
        private Color TextColor {
            get { return MainColor ?? DefaultEditTextColor; }
        }
        #endregion

        /// <summary>
        /// Символы переноса строки.
        /// </summary>
        private static char[] LineEnds = { '\n', '\r' };
        /// <summary>
        /// Получает первую строку из указанного текста.
        /// </summary>
        private static string GetFirstLine(string value) {
            if (string.IsNullOrEmpty(value))
                return string.Empty;
            int i = value.IndexOfAny(LineEnds);
            return i != -1 ? value.Substring(0, i) : value;
        }

        /// <summary>
        /// Рисует область редактора.
        /// </summary>
        public override void Draw() {
            DrawBackground();
            var point = AbsoluteLocation.Padding(2);
            DrawText(point);
            DrawTextCursor(point);
        }

        /// <summary>
        /// Рисует видимый текст.
        /// </summary>
        private void DrawText(Point point) {
            if (HasVisibleText) {
                Font.Color = TextColor;
                Font.Draw(point.X, point.Y, VisibleText);
            }
        }

        /// <summary>
        /// Рисует текстовый курсор.
        /// </summary>
        private void DrawTextCursor(Point point) {
            if (!Focused || !_cursorVisible)
                return;
            if (HasVisibleText)
                point.X += Font.GetWidth(VisibleText, 0, _pos);
            Display.DrawLine(point.X, point.Y, point.X, point.Y + Font.FontSurface.FontHeight, TextColor);
        }

        /// <summary>
        /// Обрабатывает редактор.
        /// </summary>
        public override void Process() {
            base.Process();
            if (Focused)
                CursorBlink();
        }

        /// <summary>
        /// Обрабтывает мигание текстового курсора.
        /// </summary>
        private void CursorBlink() {
            _cursorBlinkTimer++;
            if (_cursorBlinkTimer >= CursorBlinkTime) {
                _cursorBlinkTimer = 0;
                _cursorVisible = !_cursorVisible;
            }
        }

        /// <summary>
        /// Устанавливает фокус на редактор по клику.
        /// </summary>
        public override void DoClick(Mouse.MouseButtons buttons, bool down) {
            if (!Focused && buttons == Mouse.MouseButtons.Primary && down)
                SetFocus();
        }

        /// <summary>
        /// Устанавливает позицию курсора в конец при начале редактирования.
        /// </summary>
        public override void DoFocus() {
            base.DoFocus();
            CursorEnd();
        }

        /// <summary>
        /// Сбрасывает изменения при потере фокуса.
        /// </summary>
        public override void DoLostFocus() {
            base.DoLostFocus();
            EditedText = _value;
            CursorHome();
        }

        /// <summary>
        /// Обрабатывает нажатия клавиш.
        /// </summary>
        public override bool DoKeyPress(KeyboardEvent ev) {
            if (Focused) {
                switch (ev.KeyCode) {
                    case KeyCode.Left:
                        CursorLeft();
                        return true;
                    case KeyCode.Right:
                        CursorRight();
                        return true;
                    case KeyCode.Up:
                    case KeyCode.Home:
                        CursorHome();
                        return true;
                    case KeyCode.Down:
                    case KeyCode.End:
                        CursorEnd();
                        return true;
                    case KeyCode.BackSpace:
                        DeleteChar(false);
                        return true;
                    case KeyCode.Delete:
                        DeleteChar(true);
                        return true;
                    case KeyCode.Return:
                        CompleteEdit();
                        return true;
                }
                if (!string.IsNullOrEmpty(ev.KeyString)) {
                    EditedText = EditedText.Insert(CursorPosition, ev.KeyString);
                    for (int i = ev.KeyString.Length; i > 0; i--)
                        CursorRight();
                }
            }
            return base.DoKeyPress(ev);
        }

        /// <summary>
        /// Перемещает позицию курсора на 1 влево.
        /// </summary>
        private void CursorLeft() {
            if (_pos > 0)
                _pos--; else if (StartPosition > 0)
                StartPosition -= 1;
        }

        /// <summary>
        /// Перемещает позицию курсора на 1 вправо.
        /// </summary>
        private void CursorRight() {
            if (!HasVisibleText)
                return;
            if (_pos < VisibleTextLength)
                _pos++; else if (CursorPosition < EditedTextLength) {
                StartPosition += 1;
                while (_pos > VisibleTextLength) {
                    StartPosition += 1;
                    _pos--;
                }
            }
        }

        /// <summary>
        /// Перемещает позицию курсора в начало строки.
        /// </summary>
        private void CursorHome() {
            StartPosition = 0;
            _pos = 0;
        }

        /// <summary>
        /// Перемещает позицию курсора в конец строки.
        /// </summary>
        private void CursorEnd() {
            if (Font == null)
                return;
            if (EditedTextLength == (StartPosition + VisibleTextLength)) {
                _pos = VisibleTextLength;
                return;
            }
            int start = Font.GetEndPositionByWidth(EditedText, EditedTextLength, EditWidth);
            StartPosition = start;
            _pos = EditedTextLength - start;
        }

        /// <summary>
        /// Удаляет один символ около позиции курсора.
        /// </summary>
        /// <param name="after">True для удаления справа от курсора, False для удаления слева от курсора.</param>
        private void DeleteChar(bool after) {
            if (after) {
                if (CursorPosition == EditedTextLength)
                    return;
            } else {
                if (CursorPosition == 0)
                    return;
                CursorLeft();
            }
            EditedText = EditedText.Remove(CursorPosition, 1);
            if (_start == EditedTextLength && _pos == 0)
                CursorEnd();
            else
                _visibleText = null;
        }

        /// <summary>
        /// Завершает редактирование с сохранением текста.
        /// </summary>
        private void CompleteEdit() {
            if (!Focused)
                return;
            _value = EditedText;
            ResetFocus();
        }
    }
}
